package com.riven.redisson.concurrent;

import lombok.Getter;
import lombok.Setter;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.LongCodec;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * @author riven
 * @since 2025-05-22
 */
public class SetNxRedissonLockProvider implements LockProvider {

    private final RedissonClient redissonClient;
    @Setter
    @Getter
    private long maxLockMillis;

    public SetNxRedissonLockProvider(RedissonClient redissonClient) {
        this.redissonClient = redissonClient;
    }


    @Override
    public Lock getLock(Object subject) {
        return new SetNxLock(Objects.requireNonNull(subject).toString(), this.redissonClient, this.maxLockMillis);
    }

    private static class SetNxLock implements Lock {

        private final RBucket<Long> lockBucket;

        private final long lockVal;
        private final long maxLockMillis;

        public SetNxLock(String name, RedissonClient redissonClient, long maxLockMillis) {
            if (!StringUtils.hasText(name)) {
                throw new IllegalArgumentException("lock name must not be null");
            }
            this.lockBucket = redissonClient.getBucket(name, LongCodec.INSTANCE);
            this.lockVal = System.currentTimeMillis() + ThreadLocalRandom.current().nextLong(100_000_000L);
            this.maxLockMillis = maxLockMillis;
        }

        @Override
        public void lock() {
            if (!tryLock(1, TimeUnit.MINUTES)) {
                throw new IllegalMonitorStateException("lock timeout");
            }
        }

        @Override
        public void lockInterruptibly() {
            throw new UnsupportedOperationException("please see tryLock() method");
        }

        @Override
        public boolean tryLock() {
            return this.lockBucket.setIfAbsent(this.lockVal, Duration.ofMillis(maxLockMillis));
        }

        @Override
        public boolean tryLock(long time, TimeUnit unit) {
            if (time <= 0L || unit == null) {
                return tryLock();
            }
            final long deadline = System.currentTimeMillis() + unit.toMillis(time);
            while (System.currentTimeMillis() < deadline) {
                if (tryLock()) {
                    return true;
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(5);
                } catch (InterruptedException e) {
                    //ignore
                }
            }
            return false;
        }

        /**
         * 解锁时要确保解锁的是当前的锁，比如：A线程获取锁后执行时长15秒（超过10秒了，锁已自动释放），此时B线程获取了锁并执行，
         * 紧接着A线程去解锁，若直接删key，会导致C线程能获取到锁，但实际上B线程还未执行完毕，C线程理论上是不能获取到锁的
         */
        @Override
        public void unlock() {
            if (Objects.equals(this.lockBucket.get(), this.lockVal)) {
                this.lockBucket.delete();
            }
        }

        @Override
        public Condition newCondition() {
            throw new UnsupportedOperationException("newCondition was not supported");
        }
    }

}
